// CherryMerry ( Mikhail Tomilenko ), Reverse Entertainment, 2021-2024


#include "../Public/PlayerCombatManager.h"
#include <NifestGame/ActorComponents/Public/PlayerHealthManager.h>
#include <NifestGame/ActorComponents/Public/PlayerMovementManager.h>
#include <NifestGame/Interfaces/Public/I_Core.h>

#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Runtime/Engine/Public/TimerManager.h"

UPlayerCombatManager::UPlayerCombatManager()
{
	PrimaryComponentTick.bCanEverTick = false;

	MinUltimateDamage = 250.f;
	MaxUltimateDamage = 310.f;

	MinAbilityDamage = 130.f;
	MaxAbilityDamage = 180.f;

	MinAttackDamage = 42.f;
	MaxAttackDamage = 60.f;
}


void UPlayerCombatManager::BeginPlay()
{
	Super::BeginPlay();
	
	UpdateAttack();

	bBlockingDamage = false;
	bCanUseAbility = true;
}


void UPlayerCombatManager::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

}

void UPlayerCombatManager::AddProgressToUltimate(float ProgressToAdd)
{
	if (UltimateProgress != 100)
	{
		UltimateProgress += ProgressToAdd;

		if (UltimateProgress >= 100)
		{
			UltimateProgress = 100;
			bCanUseUltimate = true;
		}
	}
}

void UPlayerCombatManager::UpdateUltimate()
{
	GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Green, TEXT("Ultimate is ready!"));
	bCanUseUltimate = true;
	GetWorld()->GetTimerManager().ClearTimer(UltimateTimerHandle);
}

void UPlayerCombatManager::UpdateAbility()
{
	GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Green, TEXT("Ability is ready!"));
	bCanUseAbility = true;
	GetWorld()->GetTimerManager().ClearTimer(AbilityTimerHandle);
}

void UPlayerCombatManager::UseUltimate()
{
	if (bCanUseUltimate && UltimateProgress == 100)
	{
		bCanUseUltimate = false;
		UltimateProgress = 0;

		const FVector Start = GetOwner()->GetActorLocation();
		const FVector End = GetOwner()->GetActorLocation();

		float SphereRadius = 400.f;

		TArray<AActor*> ActorsToIgnore;
		ActorsToIgnore.Add(GetOwner());

		TArray<FHitResult> HitArray;

		TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypesArray;
		ObjectTypesArray.Emplace(ECollisionChannel::ECC_Pawn);

		const bool Hit = UKismetSystemLibrary::SphereTraceMultiForObjects(GetWorld(), Start, End, SphereRadius, ObjectTypesArray, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, HitArray, true, FColor::Blue, FColor::Red, 2.f);

		if (Hit)
		{
			for (const FHitResult HitResult : HitArray)
			{
				APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
				AActor* ActorToDamage = HitResult.Actor.Get();

				float Damage = UKismetMathLibrary::RandomFloatInRange(MinUltimateDamage, MaxUltimateDamage);
				UE_LOG(LogTemp, Warning, TEXT("%f"), Damage);

				UGameplayStatics::ApplyDamage(ActorToDamage, Damage, PlayerController, GetOwner(), UltimateDamageType);

				GetWorld()->GetTimerManager().SetTimer(UltimateTimerHandle, this, &UPlayerCombatManager::UpdateUltimate, 30.f, true);
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Hit is not valid"));
		}

	}

}

void UPlayerCombatManager::UseAbility()
{
	if (bCanUseAbility)
	{
		bCanUseAbility = false;

		const FVector Start = GetOwner()->GetActorLocation();
		const FVector End = GetOwner()->GetActorLocation();

		float SphereRadius = 350.f;

		TArray<AActor*> ActorsToIgnore;
		ActorsToIgnore.Add(GetOwner());

		TArray<FHitResult> HitArray;

		TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypesArray;
		ObjectTypesArray.Emplace(ECollisionChannel::ECC_Pawn);

		const bool Hit = UKismetSystemLibrary::SphereTraceMultiForObjects(GetWorld(), Start, End, SphereRadius, ObjectTypesArray, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, HitArray, true, FColor::Blue, FColor::Red, 2.f);

		if (Hit)
		{
			for (const FHitResult HitResult : HitArray)
			{
				APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
				AActor* ActorToDamage = HitResult.Actor.Get();

				float Damage = UKismetMathLibrary::RandomFloatInRange(MinAbilityDamage, MaxAbilityDamage);
				UE_LOG(LogTemp, Warning, TEXT("%f"), Damage);

				UGameplayStatics::ApplyDamage(ActorToDamage, Damage, PlayerController, GetOwner(), AbilityDamageType);

				GetWorld()->GetTimerManager().SetTimer(AbilityTimerHandle, this, &UPlayerCombatManager::UpdateAbility, 30.f, true);
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Hit is not valid"));
		}
	}
}

void UPlayerCombatManager::UpdateAttack()
{
	GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Green, TEXT("Attack is ready!"));
	bCanAttack = true;
	GetWorld()->GetTimerManager().ClearTimer(AttackTimerHandle);
}

void UPlayerCombatManager::BlockIncomingDamage()
{
	AActor* Owner = GetOwner();
	UPlayerHealthManager* HealthManager = Owner->FindComponentByClass<UPlayerHealthManager>();
	UPlayerMovementManager* MovementManager = Owner->FindComponentByClass<UPlayerMovementManager>();

	if (Owner && HealthManager && MovementManager && !bBlockingDamage && HealthManager->bCanDamage == true && MovementManager->bHasStamina == true)
	{
		UE_LOG(LogTemp, Warning, TEXT("Block incoming damage"));
		HealthManager->bCanDamage = false;
		MovementManager->bBlockingDamage = true;
		bBlockingDamage = true;

		II_Core* CoreInterface = Cast<II_Core>(Owner);
		if (CoreInterface)
		{
			if (MovementManager->bIsRunning)
			{
				if (MovementManager->bIsCrouching)
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 350, true);
				}
				else
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 500, false);
				}
			}
			else
			{
				if (MovementManager->bIsCrouching)
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 180, true);
				}
				else
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 200, false);
				}
			}
		}
	}
}

void UPlayerCombatManager::StopBlockIncomingDamage()
{
	AActor* Owner = GetOwner();
	UPlayerHealthManager* HealthManager = Owner->FindComponentByClass<UPlayerHealthManager>();
	UPlayerMovementManager* MovementManager = Owner->FindComponentByClass<UPlayerMovementManager>();

	if (Owner && HealthManager && MovementManager && bBlockingDamage && HealthManager->bCanDamage == false)
	{
		UE_LOG(LogTemp, Warning, TEXT("Stop block incoming damage"));
		HealthManager->bCanDamage = true;
		MovementManager->bBlockingDamage = false;
		bBlockingDamage = false;

		II_Core* CoreInterface = Cast<II_Core>(Owner);
		if (CoreInterface)
		{
			if (MovementManager->bIsRunning)
			{
				if (MovementManager->bIsCrouching)
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 450, true);
				}
				else
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 600, false);
				}
			}
			else
			{
				if (MovementManager->bIsCrouching)
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 240, true);
				}
				else
				{
					CoreInterface->Execute_UpdatePlayerSpeed(Owner, 300, false);
				}
			}
		}
	}
}

void UPlayerCombatManager::Attack()
{
	if (bCanAttack)
	{
		bCanAttack = false;

		const FVector Start = GetOwner()->GetActorLocation();
		const FVector End = GetOwner()->GetActorLocation();

		float SphereRadius = 200.f;

		TArray<AActor*> ActorsToIgnore;
		ActorsToIgnore.Add(GetOwner());

		TArray<FHitResult> HitArray;

		TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypesArray;
		ObjectTypesArray.Emplace(ECollisionChannel::ECC_Pawn);

		const bool Hit = UKismetSystemLibrary::SphereTraceMultiForObjects(GetWorld(), Start, End, SphereRadius, ObjectTypesArray, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, HitArray, true, FColor::Blue, FColor::Red, 2.f);

		if (Hit)
		{
			for (const FHitResult HitResult : HitArray)
			{
				APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
				AActor* ActorToDamage = HitResult.Actor.Get();

				float Damage = UKismetMathLibrary::RandomFloatInRange(MinAttackDamage, MaxAttackDamage);
				UE_LOG(LogTemp, Warning, TEXT("%f"), Damage);

				UGameplayStatics::ApplyDamage(ActorToDamage, Damage, PlayerController, GetOwner(), AttackDamageType);

				GetWorld()->GetTimerManager().SetTimer(AttackTimerHandle, this, &UPlayerCombatManager::UpdateAttack, 2.f, true);
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Hit is not valid"));
		}
	}
}
